package IC.LIR;

import java.util.*;
import IC.AST.*;
import IC.SymbolTable.*;


public class LIROffsets {

	public LIROffsets(ICClass icClass){
		this.icClass = icClass;
		
		/* Iterate over all methods and put in table
		 * we use a incrementing counter for offsets
		 */
		for(Method m: icClass.getMethods()){
			/* method static ? no need for offset */
			if (m.isMethodStatic())
				continue;
			methodToOffset.put(m, methodCounter++);
		}
		
		// the same with fields
		for(Field f: icClass.getFields()){
			fieldToOffset.put(f, fieldCounter++);
		}
		
		for(Method m: icClass.getMethods()){
			nameToMethod.put(m.getName(), m);
		}
	}
	
	@SuppressWarnings("unchecked")
	
	public LIROffsets (ICClass icClass, LIROffsets super_class_offsets) {
		this.icClass = icClass;
		
		methodToOffset = (HashMap<Method, Integer>)
					((HashMap<Method, Integer>)super_class_offsets.getMethodToOffsetMap()).clone();
		
		fieldToOffset = (HashMap<Field, Integer>)
					((HashMap<Field, Integer>)super_class_offsets.getFieldToOffsetMap()).clone();
		
		nameToMethod = (HashMap<String, Method>)
					((HashMap<String, Method>)super_class_offsets.getnameToMethod()).clone();
		
		/* initiate counters for both fields and methods */
		methodCounter = methodToOffset.size();
		fieldCounter = fieldToOffset.size();
		
		for (Method m: icClass.getMethods()){
			boolean isOverriden = false;
			// ignore static methods 
			
			if (m.isMethodStatic())
				continue;
			
			for (Method existingMethod: methodToOffset.keySet()){
				// override existing method
				if (m.getName().equals(existingMethod.getName())){
					int offset = methodToOffset.remove(existingMethod);
					methodToOffset.put(m, offset);
					isOverriden = true;
					break;
				}
			}
			
			// if not override - insert a new entry 
			if (!isOverriden)
				methodToOffset.put(m, methodCounter++);
		}
		
		// add new fields
		for(Field f: icClass.getFields()){
			fieldToOffset.put(f, fieldCounter++);
		}
		
		// create string to method
		for(Method m: icClass.getMethods()){
			nameToMethod.put(m.getName(), m);
		}
	}
	
	
	public ICClass getICClass(){
		return this.icClass;
	}
	
	public String getClassName(){
		return this.icClass.getName();
	}

	public Map<Method,Integer> getMethodToOffsetMap(){
		return this.methodToOffset;
	}
	
	public Integer getMethodOffset(Method m){
		return methodToOffset.get(m);
	}
	
	public Integer getMethodOffset(String name){
		return getMethodOffset(nameToMethod.get(name));
	}

	public Map<Field,Integer> getFieldToOffsetMap(){
		return this.fieldToOffset;
	}
	public Map<String,Method> getnameToMethod() {
		return this.nameToMethod;
	}
	
	public Integer getFieldOffset(Field f){
		return fieldToOffset.get(f);
	}
	
	public Map<String,Method> getNameToMethodMap(){
		return this.nameToMethod;
	}
	
	public Method getMethodFromName(String name){
		return nameToMethod.get(name);
	}
	
	public int getAllocSize(){
		return 4*(fieldToOffset.size()+1);
	}

	public void addMethodToOffset(Method m, Integer offset){
		methodToOffset.put(m, offset);
	}
	
	public void addFieldToOffset(Field f, Integer offset){
		fieldToOffset.put(f, offset);
	}

	public String getDispatchTable(){
		String dispatch = "_DV_"+icClass.getName()+": [";
		
		// insert methods' labels ordered by increasing offset
		for(int i = 0; i < methodCounter; i++){
			for (Method m: methodToOffset.keySet()){
				// if this method is static, skip
				if (m.isMethodStatic()) continue;
				
				// if the offset is correct, insert method label
				if (methodToOffset.get(m) == i){
					dispatch += "_";
					dispatch += ((ClassSymbolTable) m.getEnclosingScope()).getMySymbol().getName();
					dispatch += "_"+m.getName()+",";
					break;
				}
			}
		}
		if (dispatch.endsWith(",")) dispatch = dispatch.substring(0, dispatch.length()-1);
		dispatch += "]\n";
		
		// get all fields and offsets as comments
		String fieldsOffsets = "# fields offsets:\n";
		for(int i = 0; i < fieldCounter; i++){
			for (Field f: fieldToOffset.keySet()){
				// if the offset is correct, insert field and its offset
				if (fieldToOffset.get(f) == i){
					fieldsOffsets += "# "+f.getName()+": ";
					fieldsOffsets += i+"\n";
					break;
				}
			}
		}
		dispatch += fieldsOffsets;
		
		return dispatch;
	}
	private ICClass icClass;
	private Map<Method,Integer> methodToOffset = new HashMap<Method,Integer>();
	private Map<Field,Integer> fieldToOffset = new HashMap<Field,Integer>();
	private Map<String,Method> nameToMethod = new HashMap<String,Method>();
	private int methodCounter = 0;
	private int fieldCounter = 1;
}
